﻿//-----------------------------------------------------------------------------
// File: AVCSettings.cs
// Author: Pierre Biermann (tauron031@gmail.com)
// Project: FFMPEG audio video converter class library
// Namespace(s): FFShellAPI.FFMPEG
//
// Desc: Contains classes that provides settings for ffmpeg tools.
//       
// (c) 2007 - 2013 by Pierre Biermann
//-----------------------------------------------------------------------------

using System;
using System.Text.RegularExpressions;
using FFShellAPI.FFMPEG.Core;
using System.Collections;
using System.Xml.Serialization;

namespace FFShellAPI.FFMPEG.Settings
{
    /// <summary>
    /// Codec support flags.
    /// </summary>
    public enum CodecSupportFlags
    {
        Nothing = 0x00,
        /// <summary>
        /// encoding available
        /// </summary>
        Encoding = 0x01,
        /// <summary>
        /// decoding available
        /// </summary>
        Decoding = 0x02,
        /// <summary>
        /// audio codec
        /// </summary>
        AudioCodec = 0x04,
        /// <summary>
        /// video codec
        /// </summary>
        VideoCodec = 0x08,
        /// <summary>
        /// codec supports slices
        /// </summary>
        SupportSlices = 0x10,
        /// <summary>
        /// codec supports direct rendering
        /// </summary>
        SupportDirectRendering = 0x20,
        /// <summary>
        /// codec can handle input truncated at random locations instead of only at frame boundaries
        /// </summary>
        SupportInputTruncated = 0x40
    }

    /// <summary>
    /// Class that holds a description for a audio or video codec 
    /// that can be used by ffmpeg for encoding issues.
    /// </summary>
    [Serializable]
    public class AVCodec
    {
        /// <summary>
        /// Creates a new AVCodec instance.
        /// </summary>
        public AVCodec()
        {
            SupportChars = String.Empty;
        }

        /// <summary>
        /// Creates a new AVCodec instance.
        /// </summary>
        /// <param name="codecName">codec name</param>
        /// <param name="codecDescription">codec description</param>
        /// <param name="supportChars">codec support flags as chars from output data string</param>
        public AVCodec(string codecName, string codecDescription, string supportChars)
        {
            CodecName = codecName;
            CodecDescription = codecDescription;
            SupportChars = supportChars;
        }

        /// <summary>
        /// Returns a filtered selection of audio/video codecs.
        /// </summary>
        /// <param name="codecs">input array of audio/video codecs</param>
        /// <param name="criteria">filter criteria</param>
        /// <returns>output array with codecs</returns>
        public static AVCodec[] Filter(AVCodec[] codecs, CodecSupportFlags criteria)
        {
            ArrayList codecList = new ArrayList();
            foreach (AVCodec codec in codecs)
            {
                if ((codec.SupportFlags & criteria) == criteria)
                    codecList.Add(codec);
            }
            return (AVCodec[])codecList.ToArray(typeof(AVCodec));
        }

        /// <summary>
        /// Returns the name of the codec.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return CodecName;
        }

        /// <summary>
        /// Gets the name of audio or video codec.
        /// </summary>
        public string CodecName { get; set; }

        /// <summary>
        /// Gets the description for an audio or video codec.
        /// </summary>
        public string CodecDescription { get; set; }

        /// <summary>
        /// Gets or sets the support flags as chars.
        /// </summary>
        public string SupportChars { get; set; }

        /// <summary>
        /// Gets the support flags for the codec.
        /// </summary>
        public CodecSupportFlags SupportFlags
        {
            get
            {
                CodecSupportFlags flags = CodecSupportFlags.Nothing;
                if (SupportChars.Contains("A"))
                    flags = flags | CodecSupportFlags.AudioCodec;
                if (SupportChars.Contains("V"))
                    flags = flags | CodecSupportFlags.VideoCodec;
                if (SupportChars.Contains("E"))
                    flags = flags | CodecSupportFlags.Encoding;
                if (SupportChars.Contains("D"))
                    flags = flags | CodecSupportFlags.Decoding | CodecSupportFlags.SupportDirectRendering;
                if (SupportChars.Contains("S"))
                    flags = flags | CodecSupportFlags.SupportSlices;
                if (SupportChars.Contains("T"))
                    flags = flags | CodecSupportFlags.SupportInputTruncated;
                return flags;
            }
        }
    }

    /// <summary>
    /// Class that provides input and output file informations 
    /// for converting, probing or playing media files.
    /// </summary>
    [Serializable]
    public class BaseSettings
    {
        /// <summary>
        /// Creates a new TargetSettings instance.
        /// </summary>
        public BaseSettings()
        {
            InputFile = String.Empty;
            OverrideExisting = false;
            ForceFormat = false;
            ThreadCount = -1;
        }

        /// <summary>
        /// Returns the current part of the command line.
        /// </summary>
        /// <returns>command line string</returns>
        public override string ToString()
        {
            CommandLineWriter cmdLine = new CommandLineWriter();
            if (ThreadCount > 0)
            {
                cmdLine.Write("-threads");
                cmdLine.Write(ThreadCount.ToString());
            }
            if (OverrideExisting)
            {
                cmdLine.Write("-y");
            }
            if (ForceFormat)
            {
                cmdLine.Write("-f");
            }
            if (!String.IsNullOrWhiteSpace(InputFile))
            {
                cmdLine.Write("-i");
                cmdLine.WriteFileArgument(InputFile);
            }
            return cmdLine.ToString();
        }

        /// <summary>
        /// Sets or gets the input file name.
        /// </summary>
        public string InputFile { get; set; }

        /// <summary>
        /// Gets or the sets the number of threads that will be used for encoding issues.
        /// </summary>
        public int ThreadCount { get; set; }

        /// <summary>
        /// Sets or gets the parameter that specify whether an already existing 
        /// output file should be override or not.
        /// </summary>
        public bool OverrideExisting { get; set; }

        /// <summary>
        /// Sets or gets the value to force the ouput format.
        /// </summary>
        public bool ForceFormat { get; set; }
    }

    /// <summary>
    /// Class that holds settings for audio encoding.
    /// </summary>
    [Serializable]
    public class AudioSettings
    {
        /// <summary>
        /// Creates a new AudioSettings instance.
        /// </summary>
        public AudioSettings()
        {
            SampleRate = 44100;
            Bitrate = 128;
            Channels = 2;
            SyncBitrate = 0;
            Volume = 256;
            DisableAudio = false;
            CodecName = String.Empty;
        }

        /// <summary>
        /// Returns the current part of the command line.
        /// </summary>
        /// <returns>command line string</returns>
        public override string ToString()
        {
            CommandLineWriter cmdLine = new CommandLineWriter();
            if (!DisableAudio)
            {
                cmdLine.Write("-ar");
                cmdLine.Write(SampleRate.ToString());
                cmdLine.Write("-ac");
                cmdLine.Write(Channels.ToString());
                cmdLine.Write("-b:a");
                cmdLine.Write(Bitrate.ToString());
                cmdLine.Write('k');
                if (Volume != 256)
                {
                    cmdLine.Write("-vol");
                    cmdLine.Write(Volume.ToString());
                }
                if (SyncBitrate > 0)
                {
                    cmdLine.Write("-async");
                    cmdLine.Write(SyncBitrate.ToString());
                }
                if (!String.IsNullOrWhiteSpace(CodecName))
                {
                    cmdLine.Write("-acodec");
                    cmdLine.Write(CodecName);
                }
            }
            else
            {
                cmdLine.Write("-an");
            }
            return cmdLine.ToString();
        }

        /// <summary>
        /// Gets or sets the sample rate for the audio stream.
        /// </summary>
        public int SampleRate { get; set; }

        /// <summary>
        /// Gets or sets the bit rate for the audio stream.
        /// </summary>
        public int Bitrate { get; set; }

        /// <summary>
        /// Gets or sets the sycronisation bit rate for the audio stream.
        /// </summary>
        public int SyncBitrate { get; set; }

        /// <summary>
        /// Gets or sets the volume level for the audio stream.
        /// </summary>
        public int Volume { get; set; }

        /// <summary>
        /// Audio should be completly disable or not.
        /// </summary>
        public bool DisableAudio { get; set; }

        private int _channels;
        /// <summary>
        /// Gets or sets the number of audio channels in the audio stream (1 = mono; 2 = stereo).
        /// </summary>
        public int Channels 
        {
            get { return _channels; }
            set
            {
                _channels = value;
                if (_channels > 2) _channels = 2;
                if (_channels < 1) _channels = 1;
            }
        }

        /// <summary>
        /// Gets or sets the audio type.
        /// </summary>
        public string AudioType { get; set; }

        /// <summary>
        /// Gets or sets the name of the audio codec that should be used for encoding issues.
        /// </summary>
        public string CodecName { get; set; }
    }

    /// <summary>
    /// Screen format enumeration
    /// </summary>
    public enum VideoScreenFormat
    {
        /// <summary>
        /// None
        /// </summary>
        FormatNone,
        /// <summary>
        /// Normal (4:3)
        /// </summary>
        FormatNormal,
        /// <summary>
        /// Wide Screen (16:9)
        /// </summary>
        FormatWideScreen,
    }

    /// <summary>
    /// Class that holds settings for video stream encoding.
    /// </summary>
    [Serializable]
    public class VideoSettings
    {
        /// <summary>
        /// Creates a new VideoSettings instance.
        /// </summary>
        public VideoSettings()
        {
            Bitrate = 320;
            PixelWidth = 640;
            PixelHeight = 480;
            MaxFramesPerSeconds = -1;
            DisableVideo = false;
            CodecName = String.Empty;
        }

        private string GetPixelFormat()
        {
            return String.Format("{0}x{1}", PixelWidth, PixelHeight);
        }

        /// <summary>
        /// Returns the current part of the command line.
        /// </summary>
        /// <returns>command line string</returns>
        public override string ToString()
        {
            CommandLineWriter cmdLine = new CommandLineWriter();
            if (!DisableVideo)
            {
                if (MaxFramesPerSeconds > 0)
                {
                    cmdLine.Write("-r");
                    cmdLine.Write(MaxFramesPerSeconds.ToString());
                }
                cmdLine.Write("-b:v");
                cmdLine.Write(Bitrate.ToString());
                cmdLine.Write('k');
                cmdLine.Write("-s");
                cmdLine.Write(GetPixelFormat());
                if (!String.IsNullOrWhiteSpace(CodecName))
                {
                    cmdLine.Write("-vcodec");
                    cmdLine.Write(CodecName);
                }
                if (ScreenFormat == VideoScreenFormat.FormatNormal)
                {
                    cmdLine.Write("-aspect 4:3");
                }
                if (ScreenFormat == VideoScreenFormat.FormatWideScreen)
                {
                    cmdLine.Write("-aspect 16:9");
                }
            }
            else
            {
                cmdLine.Write("-vn");
            }
            return cmdLine.ToString();
        }

        /// <summary>
        /// Gets or sets the video bitrate for the video stream. The unit of this value is kbit/s.
        /// </summary>
        public int Bitrate { get; set; }

        /// <summary>
        /// Gets or sets the pixel width for the output screen format.
        /// </summary>
        public int PixelWidth { get; set; }

        /// <summary>
        /// Gets or sets the pixel height for the output screen format.
        /// </summary>
        public int PixelHeight { get; set; }

        /// <summary>
        /// Gets or sets the name of the video codec that should be used for encoding issues.
        /// </summary>
        public string CodecName { get; set; }

        /// <summary>
        /// Specify whether the output screen format should be forced to 4:3, 16:9 or None to leave it unchanged.
        /// </summary>
        public VideoScreenFormat ScreenFormat { get; set; }

        /// <summary>
        /// Gets or sets the video type.
        /// </summary>
        public string VideoType { get; set; }

        /// <summary>
        /// Gets or sets the maximal frame rate for a new output stream.
        /// </summary>
        public int MaxFramesPerSeconds { get; set; }

        /// <summary>
        /// Video should be completly disable or not.
        /// </summary>
        public bool DisableVideo { get; set; }

        /// <summary>
        /// Gets or sets the screen pixel format as string in the manner of "width x height".
        /// </summary>
        public string VideoResolution
        {
            get { return GetPixelFormat(); }
            set
            {
                Regex rex = new Regex("(\\d{2,4})[X|x](\\d{2,4})", RegexOptions.Compiled);
                Match m = rex.Match(value);
                if (m.Success)
                {
                    PixelWidth = Int32.Parse(m.Groups[1].Value);
                    PixelHeight = Int32.Parse(m.Groups[2].Value);
                }
            }
        }
    }

    /// <summary>
    /// Serializeable time class.
    /// </summary>
    [Serializable]
    public struct TimeStamp
    {
        double _time;

        /// <summary>
        /// Creates a new TimeStamp instance.
        /// </summary>
        /// <param name="seconds">time value in seconds</param>
        public TimeStamp(double seconds)
        {
            _time = seconds;
        }

        /// <summary>
        /// Returns a zero time stamp.
        /// </summary>
        public static TimeStamp Zero
        {
            get { return new TimeStamp(0d); }
        }

        /// <summary>
        /// Sets the time value from a TimeSpan instance.
        /// </summary>
        /// <param name="tspan">TimeSpan instance</param>
        public static TimeStamp FromTimeSpan(TimeSpan tspan)
        {
            return new TimeStamp(SecondsFromTimeSpan(tspan));
        }

        /// <summary>
        ///  Creates a new TimeStamp instance.
        /// </summary>
        /// <param name="tspan">TimeSpan instance</param>
        public TimeStamp(TimeSpan tspan)
        {
            _time = SecondsFromTimeSpan(tspan);
        }

        private static double SecondsFromTimeSpan(TimeSpan time)
        {
            double dtime = time.Hours * 3600d + time.Minutes * 60d + time.Seconds;
            return dtime;
        }

        /// <summary>
        /// Returns the given time value as a short time string.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            TimeSpan tm = TimeSpanValue;
            return String.Format("{0:00}:{1:00}:{2:00}", tm.Hours, tm.Minutes, tm.Seconds);
        }

        /// <summary>
        /// addintion operator.
        /// </summary>
        /// <param name="t1">Time1</param>
        /// <param name="t2">Time2</param>
        /// <returns>Time1 + Time2</returns>
        public static TimeStamp operator +(TimeStamp t1, TimeStamp t2)
        {
            return new TimeStamp(t1.Seconds + t2.Seconds);
        }

        /// <summary>
        /// Subtration operator.
        /// </summary>
        /// <param name="t1">Time1</param>
        /// <param name="t2">Time2</param>
        /// <returns>Time1 - Time2</returns>
        public static TimeStamp operator -(TimeStamp t1, TimeStamp t2)
        {
            return new TimeStamp(t1.Seconds - t2.Seconds);
        }

        /// <summary>
        /// Gets or sets the time value from or to a TimeSpan object.
        /// </summary>
        [XmlIgnoreAttribute]
        public TimeSpan TimeSpanValue
        {
            get { return TimeSpan.FromSeconds(_time); }
        }

        /// <summary>
        /// Gets or sets the time value in seconds.
        /// </summary>
        public double Seconds
        {
            get { return _time; }
            set { _time = value; }
        }
    }

    /// <summary>
    /// Class that hold settings for part selection of any media content.
    /// </summary>
    [Serializable]
    public class RangeSettings
    {
        /// <summary>
        /// Creates a new RangeSettings instance.
        /// </summary>
        public RangeSettings()
        {
            Position = new TimeStamp();
            Duration = new TimeStamp();
        }

        /// <summary>
        /// Returns the current part of the command line.
        /// </summary>
        /// <returns>command line string</returns>
        public override string ToString()
        {
            CommandLineWriter cmdLine = new CommandLineWriter();
            cmdLine.Write("-ss");
            cmdLine.Write(Position.ToString());
            cmdLine.Write("-t");
            cmdLine.Write(Duration.ToString());
            return cmdLine.ToString();
        }

        /// <summary>
        /// Gets or sets the starting position of the stream part that should be extracted.
        /// </summary>
        public TimeStamp Position { get; set; }

        /// <summary>
        /// Gets or sets the end position of the stream part that should be extracted.
        /// </summary>
        public TimeStamp Duration { get; set; }
    }

    /// <summary>
    /// Class that contains settings that can be used for extracting a single image from a video stream.
    /// </summary>
    [Serializable]
    public class ThumbnailSettings
    {
        VideoSettings vsetting;

        /// <summary>
        /// Creates a new ThumbnailSettings instance.
        /// </summary>
        public ThumbnailSettings()
        {
            vsetting = new VideoSettings();
            Position = new TimeSpan();
        }

        /// <summary>
        /// Returns the current part of the command line.
        /// </summary>
        /// <returns>command line string</returns>
        public override string ToString()
        {
            CommandLineWriter cmdLine = new CommandLineWriter();
            cmdLine.Write("-ss");
            cmdLine.Write(Position.ToString());
            cmdLine.Write("-f image2 -vframes 1");
            cmdLine.Write("-s");
            cmdLine.Write(vsetting.VideoResolution);
            return cmdLine.ToString();
        }

        /// <summary>
        /// Gets or sets the time position on with the frame should be extracted.
        /// </summary>
        public TimeSpan Position { get; set; }

        /// <summary>
        /// Gets or sets the pixel width for the output screen format.
        /// </summary>
        public int PixelWidth 
        {
            get { return vsetting.PixelWidth; }
            set { vsetting.PixelWidth = value; }
        }

        /// <summary>
        /// Gets or sets the pixel height for the output screen format.
        /// </summary>
        public int PixelHeight
        {
            get { return vsetting.PixelHeight; }
            set { vsetting.PixelHeight = value; }
        }

        /// <summary>
        /// Gets or sets the screen pixel format as string in the manner of "width x height".
        /// </summary>
        public string ScreenFormat
        {
            get { return vsetting.VideoResolution; }
            set { vsetting.VideoResolution = value; }
        }
    }

    /// <summary>
    /// Class that holds all part settings for audio or video file conversions.
    /// </summary>
    [Serializable]
    public class ConversionSettings
    {
        /// <summary>
        /// Creates a new ConversionSettings instance.
        /// </summary>
        public ConversionSettings()
        {
            TargetSettings = new BaseSettings();
            AudioSettings = new AudioSettings();
            VideoSettings = new VideoSettings();
        }

        /// <summary>
        /// Returns the current part of the command line.
        /// </summary>
        /// <returns>command line string</returns>
        public override string ToString()
        {
            CommandLineWriter cmdLine = new CommandLineWriter();
            if (AudioSettings != null)
            {
                cmdLine.Write(AudioSettings.ToString());
            }
            if (VideoSettings != null)
            {
                cmdLine.Write(VideoSettings.ToString());
            }
            if (TargetSettings != null)
            {
                cmdLine.Write(TargetSettings.ToString());
            }
            return cmdLine.ToString();
        }

        /// <summary>
        /// Gets or sets the target settings.
        /// </summary>
        public BaseSettings TargetSettings { get; set; }

        /// <summary>
        /// Gets or sets the audio settings.
        /// </summary>
        public AudioSettings AudioSettings { get; set; }

        /// <summary>
        /// Gets or sets the video settings.
        /// </summary>
        public VideoSettings VideoSettings { get; set; }
    }
}
